#include "stdafx.h"
#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#include "filelist.h"
#include "filethread.h"

#include "wx/listimpl.cpp"
WX_DEFINE_LIST(FileInfoList);

#include "wx/dir.h"
#include "wx/filename.h"
#include "wx/file.h"
//#include "wx/wfstream.h"
#include "wx/utils.h"
//#include "wx/zstream.h"
#include "wx/log.h"
#include "wx/datetime.h"
#include "ini.h"
#include "exception.h"
#include "main.h"
#include <windows.h>
#include "log.h"

//wxString files(_T("/files/"));


/*
drwxrwxr-x   2 10000  10000    512 Jun 18  2001 JUNK
-rwxrwxr--   1 10000  10000   1054 Nov 22  2000 addnews.phtml
*/ 

wxString getFileName(const wxString& str)
{
	return str.AfterLast(' ');
}

wxString getFileSize(const wxString& str)
{
	wxString result;
	short int c = 0;
	for( unsigned int i = 0; i < str.Length(); i++ )
	{
		if( str[i] == ' ' )
		{
			++c;
			while( str[i] == ' ' && i < str.Length() )
				++i;
		}
		if( c == 4 )
		{
			result = str.Mid(i);
			result = result.BeforeFirst(' ');
			break;
		}
	}
	return result;
}

wxString completePath(const wxString& path)
{
	wxString result(path);
	return result;
}

/* Files */

void Files::checkTerminated(void)
{
	fth->checkTerminated();
}

void Files::filClear(FileInfoList *fil)
{
	for( int i = fil->GetCount() - 1; i >= 0; i-- )
	{
		delete fil->Item(i);
	}
	fil->Clear();
}

// adds to 'this' items that absent in 'this' and presents in 'src'
DWORD Files::Add(Files *src, bool write_files)
{
	FileInfoList *filSrc = src->fiList;
	FileInfoList *filDst = fiList;

	wxString s;
	
	FileInfoList::Node *found;
	FileInfo *fiSrc, *fiDst;
	//wxZlibInputStream* zis;
	//wxZlibOutputStream* zos;
	wxStreamError se;
	wxString srcFN, dstFN;
	bool equal;
	wxFileName f;
	DWORD bytes_written = 0, bw = 0;
	for( FileInfoList::Node *n = filSrc->GetFirst(); n; n = n->GetNext() )
	{
		checkTerminated();
		fiSrc = n->GetData();
		found = filDst->Find(fiSrc->sName);
		if( found )
		{
			fiDst = found->GetData();
		}
		else
		{
			fiDst = new FileInfo(fiSrc);
			filDst->Append(fiDst->sName, fiDst);
		}
		if( cp->bCSize )
			equal = fiSrc->uiSizeHigh == fiDst->uiSizeHigh && fiSrc->uiSizeLow == fiDst->uiSizeLow;
		else
			equal = *fiDst == *fiSrc;
		if( !found || !equal )
		{
			if( write_files )
			{
				checkTerminated();
				HANDLE is = src->GetInputStream(fiSrc);
				HANDLE os = GetOutputStream(fiDst);
				
				Log::Info(fiDst->sName);

				s.Clear();
				if( !found )
					s << (s.Length() ? wxT(" ") : wxT("")) << wxT("absent");
				else
				{
					if( !equal )
					//print the reason - what is not match in files
					{
							if( !(fiSrc->dtCreated.IsEqualTo(fiDst->dtCreated)) )
								s << (s.Length() ? wxT(" ") : wxT("")) << wxT("dcreat ") << fiSrc->dtCreated.Format(wxT("[%d.%m.%y %H:%M]/")) << fiDst->dtCreated.Format(wxT("[%d.%m.%y %H:%M]"));
							if( !(fiSrc->dtModified.IsEqualTo(fiDst->dtModified)) )
								s << (s.Length() ? wxT(" ") : wxT("")) << wxT("dmodif ") << fiSrc->dtModified.Format(wxT("[%d.%m.%y %H:%M]/")) << fiDst->dtModified.Format(wxT("[%d.%m.%y %H:%M]"));
							if( fiSrc->uiSizeLow != fiDst->uiSizeLow )
								s << (s.Length() ? wxT(" ") : wxT("")) << wxT("size [") << fiSrc->uiSizeLow << wxT("]/[") << fiDst->uiSizeLow << wxT("]");
							if( fiSrc->dwAttr != fiDst->dwAttr )
								s << (s.Length() ? wxT(" ") : wxT("")) << wxT("attr [") << fiSrc->dwAttr << wxT("]/[") << fiDst->dwAttr << wxT("]");
					}
				}
				Log::Info(wxT("r(%s) "), s.c_str().AsWChar());
				try
				{
					bw = copyFile(is, os);				
				}
				catch(Exception &ex)
				{
					DeleteFile(cp->sDestination+fiDst->sName);
					Log::Info(wxT(" deleted"));
					throw;
				}
				if( cp->bVerbose )
				{
					Log::Info(wxT("%i"), bw);
				}
				bytes_written += bw;

				// set original file attributes
				s = sPath + fiDst->sName;
				f.SetName(s);
				if( !f.SetTimes(NULL, &fiSrc->dtModified, &fiSrc->dtCreated) )
				{
					Log::Info(wxT("%s cann\'t set file time \'%s\'"), err_str, s.c_str().AsWChar());
				}
#if defined(__WINDOWS__)
				if( !SetFileAttributes(s.c_str(), fiSrc->dwAttr) )
				{
					Log::Info(wxT("%s cann\'t set file attributes \'%s\', error %i"), err_str, s.c_str().AsWChar(), GetLastError());
				}
#endif //__WINDOWS__
			}
		}
	}	
	return bytes_written;
}

//replace info from 'src' items that found in 'dst'
void Files::Replace(Files *src)
{
	FileInfoList *filSrc = src->fiList;
	FileInfoList *filDst = fiList;
	
	FileInfoList::Node *found;
	FileInfo *fiSrc, *fiDst;
	for( FileInfoList::Node *n = filDst->GetFirst(); n; n = n->GetNext() )
	{
		fiDst = n->GetData();
		found = filSrc->Find(fiDst->sName);
		if( found )
		{
			fiSrc = found->GetData();
			*fiDst = *fiSrc;			
		}
		checkTerminated();
	}	
}

//delete from 'this' items that not found in 'src'
void Files::Remove(Files *src, bool write_files)
{
	FileInfoList *filSrc = src->fiList;
	FileInfoList *filDst = fiList;
	
	FileInfoList::Node *found;
	FileInfo *fiSrc, *fiDst;
	for( FileInfoList::Node *n = filDst->GetFirst(); n; n = n->GetNext() )
	{
		fiDst = n->GetData();
		found = filSrc->Find(fiDst->sName);
		if( !found && write_files )
		{		
			if( DeleteFile(cp->sDestination+fiDst->sName) )
			{
				if( cp->bVerbose )
				{
					Log::Info(wxT(" \'%s\' deleted"), fiDst->sName.c_str().AsWChar());
				}
			}
			else
			{
				Log::Info(wxT("%s cann\'t delete file \'%s\'"), err_str, fiDst->sName.c_str().AsWChar());
			}
			delete fiDst;
			filDst->DeleteNode(n);
		}
		checkTerminated();
	}	
}

void Files::createDir(const wxString& dn)
{
	if( cp->bVerbose )
	{
		Log::Info(wxT(" creating \'%s\'"), dn.c_str());
	}
	if( !CreateDirectory(dn.c_str(), NULL) )
	{
		DWORD result = GetLastError();
		wxString str;
		str.sprintf(wxT("%s cann\'t create directory \'%s\' %i"), err_str, dn.c_str().AsWChar(), result);
		Log::Info(str);
		//if( result == ERROR_DISK_FULL )
		throw Exception(str);
	}
}

void Files::MakeDir(wxString path)
{
	//path.Replace("\\", "/");
	wxString dn, s;
	const wxChar *c = path.c_str();
	//printf("\n checking %s", path.c_str());
	bool fnp; // full network path
	wxString pathEnd(wxT("\\\\"));
	while( *c )
	{
		if( *c == _T('\\') )
		{
			fnp = dn.Length() > 2;
			if( fnp && dn.Find(pathEnd.c_str()) == 0 )
			{
				s = dn.Mid(2);
				fnp = fnp && s.Find(_T("\\")) > 0;
			}
			if( fnp && !wxDirExists(dn) )
			{
				createDir(dn);
			}
		}
		dn += *c;
		++c;
	}

	if( !wxDirExists(dn) )
	{
		createDir(dn);
	}
}

/* LocalFiles */

DWORD LocalFiles::copyFile(HANDLE is, HANDLE os)
{
	DWORD bytes_total = 0;
	try
	{
		if( is >= 0 && os >= 0 )
		{
			char *buffer = new char[cp->iBufSize];
			DWORD bytes_read, bytes_write;
			while( ReadFile(is, buffer, cp->iBufSize, &bytes_read, NULL) > 0 )
			{
				bytes_total += bytes_read;
				if( bytes_read == 0 )
					break;
				if( WriteFile(os, buffer, bytes_read, &bytes_write, NULL) == 0 )
				{
					bytes_read = GetLastError();
					break;
				}
				checkTerminated();
			}
			delete[] buffer;
			if( bytes_read )
			{
				// got error
				LPVOID lpMsgBuf;
				FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,NULL, bytes_read, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL);
				wxString error((wxChar*)lpMsgBuf);
				LocalFree(lpMsgBuf);
				if( bytes_read == ERROR_DISK_FULL )
					throw Exception(error);
				Log::Info(wxT(" %s %s"), err_str, error.c_str().AsWChar());
			}
		}
		if( os )
			CloseHandle(os);
		if( is )
			CloseHandle(is);
	}
	catch(Exception &ex)
	{
		if( os )
			CloseHandle(os);
		if( is )
			CloseHandle(is);
		throw;
	}
	return bytes_total;
}

void LocalFiles::FillFilesInfo(void)
{
	FindFiles(sPath);
}

void LocalFiles::FindDirs(const wxString &from)
{
	wxDir dir(from);
	wxString found_dir, search_in, s(wxGetCwd()), se;
	bool cont = dir.GetFirst(&found_dir, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN);
	while( cont )
	{
		se = from+ wxFILE_SEP_PATH +found_dir;
		if( !cp->reEDirs.IsValid() || !(cp->reEDirs.Matches(found_dir.c_str()) || cp->reEDirs.Matches(se.c_str())) )
		{
			search_in = from + wxFILE_SEP_PATH + found_dir;
			// this is check if we could read dir contents
			// you may comment this lines to increase speed
			if( !wxSetWorkingDirectory(search_in) )
			{
				Log::Info(wxT("%s access denied to \'%s\'"), err_str, search_in.c_str().AsWChar());
			}
			else
				wxSetWorkingDirectory(s);
			// end check read dir content
			FindFiles(search_in);
		}
		else
		{
			if( cp->bVerbose )
			{
				Log::Info(wxT(" excluding \'%s\' found in \'%s\'"), found_dir.c_str().AsWChar(), from.c_str().AsWChar());
			}
		}
		cont = dir.GetNext(&found_dir);
		checkTerminated();
	}
}

void LocalFiles::FindFiles(const wxString &from)
{
	wxString found_file;
	wxDir dir;
	if (!dir.Exists(from) && _createFirst)
		wxDir::Make(from);
	wxString s;
	wxFileName f;
	wxFile file;
	FileInfo *fi;
	wxDateTime::wxDateTime_t hour, minute;
#ifdef __WINDOWS__
	WIN32_FILE_ATTRIBUTE_DATA fad;
	GET_FILEEX_INFO_LEVELS fil;
#endif
	bool cont = dir.Open(from) && dir.GetFirst(&found_file, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN);
	while( cont )
	{
		checkTerminated();
		if( cp->reIFiles.Matches(found_file.c_str()) && ( !cp->reEFiles.IsValid() || !cp->reEFiles.Matches(found_file.c_str())) )
		{
			s = from + wxFILE_SEP_PATH + found_file;
			fi = new FileInfo();
			f.SetName(s);
			if( !f.GetTimes(NULL, &fi->dtModified, &fi->dtCreated) )
			{
				Log::Info(wxT("%s GetTimes \'%s\' failed."), err_str, s.c_str().AsWChar());
			}
			// because of lack in MS filesystems file time seconds and miliseconds will be reset to zero
			// see SetFileTime in MS SDK for details
			fi->dtModified.Set(fi->dtModified.GetDay(), fi->dtModified.GetMonth(), fi->dtModified.GetYear(), fi->dtModified.GetHour(), fi->dtModified.GetMinute(), 0, 0);
			fi->dtCreated.Set(fi->dtCreated.GetDay(), fi->dtCreated.GetMonth(), fi->dtCreated.GetYear(), fi->dtCreated.GetHour(), fi->dtCreated.GetMinute(), 0, 0);
			fi->sName = s.Mid(sPath.Length());

#if !defined(FINAL)
			Log::Info(wxT(" found \'%s\'"), s.c_str().AsWChar());
#endif //FINAL

#if defined(__WINDOWS__)
			// I think native code will be faster to get file attributes and size
			//fi->sName.Replace("\\", "/");
			//s.Replace("/", "\\");
			if( GetFileAttributesEx(s.c_str(), GetFileExInfoStandard, &fad) )
			{
				fi->dwAttr = fad.dwFileAttributes & ~FILE_ATTRIBUTE_COMPRESSED;
				fi->uiSizeHigh = fad.nFileSizeHigh;
				fi->uiSizeLow = fad.nFileSizeLow;
			}
#if !defined(FINAL)
			else
			{
				Log::Info(wxT("%s GetFileAttributes \'%s\' failed. Error code %i"), err_str, s.c_str().AsWChar(), GetLastError());
			}
#endif //FINAL
#else //__WINDOWS__
			file.Open(s.c_str());
			fi->uiSizeLow = file.SeekEnd(0);
			// TODO
			// check is wxFile::SeekEnd works with large files?
			fi->uiSizeHigh = 0;
			file.Close();
#endif //__WINDOWS__

			fiList->Append(fi->sName, fi);
		}
		cont = dir.GetNext(&found_file);
	}
	FindDirs(from);
}

HANDLE LocalFiles::GetInputStream(FileInfo* fi)
{
	wxString s(sPath);
	s += fi->sName;

	HANDLE is = CreateFile(s.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);	
	if( is == 0 )
	{
		Log::Info(wxT("%s LocalFiles::GetInputStream error %i %s"), err_str, GetLastError(), s.c_str().AsWChar());
	}
	return is;
}

HANDLE LocalFiles::GetOutputStream(FileInfo* fi)
{
	wxString fn;
	fn << sPath << fi->sName;
	MakeDir(fn.BeforeLast('\\'));

#if defined(__WINDOWS__)
	if( wxFileExists(fn) )
		if( !SetFileAttributes(fn.c_str(), 0) )
		{
			Log::Info(wxT("%s cann\'t clear file attributes %s, error %i"), err_str, fn.c_str().AsWChar(), GetLastError());
		}
#endif //__WINDOWS__

	HANDLE os = CreateFile(fn.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);	
	if( os == 0 )
	{
		Log::Info(wxT("%s LocalFiles::GetOutputStream error %i %s"), err_str, GetLastError(), fn.c_str().AsWChar());
	}
	return os;
}

bool LocalFiles::DeleteFile(const wxString &fn)
{
#if defined(__WINDOWS__)
	if( !SetFileAttributes(fn.c_str(), 0) )
	{
		Log::Info(wxT("%s cann\'t clear file attributes \'%s\', error %i"), err_str, fn.c_str().AsWChar(), GetLastError());
	}
#endif //__WINDOWS__
	return wxRemoveFile(fn);
}

/*void LocalFiles::MkDir(const wxString &dn)
{
	wxMkdir(dn);
}*/

void LocalFiles::RmDir(const wxString &dn)
{
	wxRmdir(dn);
}

bool LocalFiles::DirExists(const wxString &dn)
{
	return wxDirExists(dn);
}


